package fr.inserm.um915.bdibd.model;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.HeadlessException;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;

import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
import java.util.regex.Pattern;




import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JDesktopPane;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSpinner;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.InternalFrameAdapter;
import javax.swing.event.InternalFrameEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;

import com.sleepycat.je.Transaction;


class RangeSegment
	{
	public byte chrom;
	public int chromStart;
	public int chromEnd;
	public double count;
	public double total;
	boolean below;
	}

@SuppressWarnings("serial")
class MyTableCellRenderer extends DefaultTableCellRenderer
	{
	private Color COLOR0=new Color(241,241,241);
	private Color COLOR1=new Color(255,234,232);
	MyTableCellRenderer()
		{
		setOpaque(true);
		}
	@Override
	public Component getTableCellRendererComponent(JTable table, Object value,
			boolean isSelected, boolean hasFocus, int row, int column) {
		Component c=super.getTableCellRendererComponent(table, value, isSelected, hasFocus,
				row, column);
		if(!isSelected && !hasFocus)
			{
			setBackground(row%2==0?COLOR0:COLOR1);
			}
		return c;
		}
	}

@SuppressWarnings("serial")
abstract class GenericTableModel<T>
	extends AbstractTableModel
	{
	
	public abstract T getElementAt(int index);
	public abstract Object getValueOf(T o,int columnIndex);
	@Override
	public boolean isCellEditable(int rowIndex, int columnIndex) {
		return false;
		}
	
	@Override
	public Object getValueAt(int rowIndex, int columnIndex) {
		return getValueOf(getElementAt(rowIndex),columnIndex);
		}
	
	}
/**
 * AbstractChromStartEndModel
 * holds a list of chrom/start/end
 */
@SuppressWarnings("serial")
abstract class AbstractChromStartEndTableModel
	extends GenericTableModel<ChromStartEnd>
	{
	
	protected AbstractChromStartEndTableModel()
		{
		}
	@Override
	public int getColumnCount() {
		return 3;
		}
	@Override
	public String getColumnName(int column)
		{
		switch(column)
			{
			case 0: return "Chromosome"; 
			case 1: return "Start";
			case 2: return "End"; 
			default: return "$"+(column+1);
			}
		}
	@Override
	public Class<?> getColumnClass(int column)
		{
		switch(column)
			{
			case 0: return String.class; 
			case 1: return Integer.class;
			case 2: return Integer.class; 
			default: return Object.class;
			}
		}
	@Override
	public Object getValueOf(ChromStartEnd m, int column)
		{
		if(m==null) return null;
		switch(column)
			{
			case 0: return m.getChromosome();
			case 1: return m.getStart();
			case 2: return m.getEnd(); 
			default: return null;
			}
		}
	public abstract List<ChromStartEnd> asList();
	}

/**
 * Default implementation of 
 * AbstractChromStartEndModel
 */
@SuppressWarnings("serial")
class DefaultChromStartEndTableModel
	extends AbstractChromStartEndTableModel
	{
	private List<ChromStartEnd> data;
	
	public DefaultChromStartEndTableModel(List<ChromStartEnd> L)
		{
		data=new Vector<ChromStartEnd>(L);
		}
	@Override
	public ChromStartEnd getElementAt(int index) {
		return data.get(index);
		}
	@Override
	public int getRowCount()
		{
		return data.size();
		}
	@Override
	public List<ChromStartEnd> asList()
		{
		return Collections.unmodifiableList(this.data);
		}
	}

/**
 * 
 * PedigreeTableModel
 *
 */
@SuppressWarnings("serial")
class PedigreeTableModel extends GenericTableModel<Individual>
	{
	private int rowCount=0;
	private Model model=null;
	private Transaction txn;
	PedigreeTableModel(Transaction txn,Model model,int rowCount)
		{
		this.txn=txn;
		this.model=model;
		this.rowCount=rowCount;
		}
	
	@Override
	public int getColumnCount() {
		return 6;
		}
	@Override
	public Individual getElementAt(int index) {
		return model.getIndividualByIndex(this.txn,index);
		}
	
	@Override
	public Object getValueOf(Individual ind, int columnIndex)
		{
		if(ind==null) throw new RuntimeException(""+columnIndex+"/"+getRowCount());
		switch(columnIndex)
			{
			case 0: return ind.getFamily();
			case 1: return ind.getName();
			case 2: return ind.getFatherId();
			case 3: return ind.getMotherId();
			case 4: return ind.getGender();
			case 5: return ind.getStatus();
			}
		return null;
		}
	
	@Override
	public String getColumnName(int columnIndex)
		{
		switch(columnIndex)
			{
			case 0: return "Family";
			case 1: return "Name";
			case 2: return "Father";
			case 3: return "Mother";
			case 4: return "Gender";
			case 5: return "Status";
			}
		return null;
		}
	
	@Override
	public Class<?> getColumnClass(int columnIndex)
		{
		switch(columnIndex)
			{
			case 0: return String.class;
			case 1: return String.class;
			case 2: return String.class;
			case 3: return String.class;
			case 4: return Integer.class;
			case 5: return Integer.class;
			}
		return null;
		}
	
	@Override
	public int getRowCount()
		{
		return this.rowCount;
		}
	}

/**
 * 
 * PedigreeTableModel
 *
 */
@SuppressWarnings("serial")
class RangeSegmentTableModel extends GenericTableModel<RangeSegment>
	{
	private List<RangeSegment> data;
	RangeSegmentTableModel(List<RangeSegment> segs)
		{
		this.data=Collections.unmodifiableList(segs);
		}
	
	@Override
	public int getColumnCount() {
		return 6;
		}
	
	@Override
	public RangeSegment getElementAt(int index) {
		return data.get(index);
		}
	
	@Override
	public Object getValueOf(RangeSegment r, int columnIndex)
		{
		switch(columnIndex)
			{
			case 0: return ChromPosition.denormalize(r.chrom);
			case 1: return r.chromStart;
			case 2: return r.chromEnd;
			case 3: return (r.below?'-':'+');
			case 4: return r.count;
			case 5: return (r.count==0 ?null:r.total/r.count);
			}
		return null;
		}
	
	@Override
	public String getColumnName(int columnIndex)
		{
		switch(columnIndex)
			{
			case 0: return "Chrom";
			case 1: return "Start";
			case 2: return "End";
			case 3: return "In/Out";
			case 4: return "Count";
			case 5: return "Mean";
			}
		return null;
		}
	
	@Override
	public Class<?> getColumnClass(int columnIndex)
		{
		switch(columnIndex)
			{
			case 0: return String.class;
			case 1: return Integer.class;
			case 2: return Integer.class;
			case 3: return Character.class;
			case 4: return Integer.class;
			case 5: return Double.class;
			}
		return null;
		}
	
	@Override
	public int getRowCount()
		{
		return this.data.size();
		}
	}


@SuppressWarnings("serial")
class MarkerTableModel extends GenericTableModel<Marker>
	{
	private int rowCount=0;
	private Model model=null;
	private Transaction txn;
	MarkerTableModel(Transaction txn,Model model,int rowCount)
		{
		this.txn=txn;
		this.model=model;
		this.rowCount=rowCount;
		}
	
	@Override
	public int getColumnCount() {
		return 4;
		}
	@Override
	public Marker getElementAt(int index) {
		return model.getMarkerByIndex(this.txn,index);
		}
	
	@Override
	public Object getValueOf(Marker o, int columnIndex)
		{
		if(o==null) throw new RuntimeException(""+columnIndex+"/"+getRowCount());
		switch(columnIndex)
			{
			case 0: return o.getName();
			case 1: return o.getPosition().getChromosome();
			case 2: return o.getPosition().getPosition();
			case 3: return o.getGenMapAsString();
			default: return null;
			}
		}
	
	@Override
	public String getColumnName(int columnIndex)
		{
		switch(columnIndex)
			{
			case 0: return "Name";
			case 1: return "Chrom";
			case 2: return "Position";
			case 3: return "GenMap";
			default:return null;
			}
		}
	
	@Override
	public Class<?> getColumnClass(int columnIndex)
		{
		switch(columnIndex)
			{
			case 0: return String.class;
			case 1: return String.class;
			case 2: return Integer.class;
			case 3: return String.class;
			default:return null;
			}
		}
	
	@Override
	public int getRowCount()
		{
		return this.rowCount;
		}
	}


@SuppressWarnings("serial")
class PairOfIndividualsTableModel extends AbstractTableModel
	{
	private int rowCount=0;
	private Model model=null;
	private Transaction txn;
	PairOfIndividualsTableModel(Transaction txn,Model model,int rowCount)
		{
		this.txn=txn;
		this.model=model;
		this.rowCount=rowCount;
		}
	
	@Override
	public int getColumnCount() {
		return 8;
		}
	
	
	
	@Override
	public Object getValueAt(int rowIndex, int columnIndex)
		{
		PairOfIndividual p=model.getPairOfIndividualByPairId(txn, rowIndex);
		if(p==null) throw new RuntimeException(""+columnIndex+"/"+getRowCount());
		switch(columnIndex)
			{
			case 0: return p.get(0).getFamily();
			case 1: return p.get(0).getName();
			//
			case 2: return p.get(1).getName();
			case 3: return p.get(1).getFamily();
			//
			case 4: return p.getConcordance();
			case 5:
			case 6:
			case 7:
				{
				IBDStatus theo=model.getTheoriticalIBDStatusByPairId(txn, rowIndex);
				if(theo==null)
					{
					return null;
					}
				return theo.getIbd(columnIndex-5);
				}
			}
		return null;
		}
	
	@Override
	public String getColumnName(int columnIndex)
		{
		switch(columnIndex)
			{
			case 0: return "I1::Family";
			case 1: return "I1::Name";
			//
			case 2: return "I2::Name";
			case 3: return "I2::Family";
			//
			case 4: return "Concordance";
			case 5: return "Theo:IBD0";
			case 6: return "Theo:IBD1";
			case 7: return "Theo:IBD2";
			default: return null;
			}
		}
	
	@Override
	public Class<?> getColumnClass(int columnIndex)
		{
		switch(columnIndex)
			{
			case 0: return String.class;
			case 1: return String.class;
			case 2: return String.class;
			case 3: return String.class;
			//
			case 4: return PairOfIndividual.Concordance.class;
			case 5: return Float.class;
			case 6: return Float.class;
			case 7: return Float.class;
			default: return null;
			}
		}
	
	@Override
	public int getRowCount()
		{
		return this.rowCount;
		}
	}


/**
 * 
 * LinkageTableModel
 *
 */
@SuppressWarnings("serial")
class LinkageTableModel extends AbstractTableModel
	{
	static final int COUNT_COLUMN_LEFT=4;
	private static final Logger LOG=Logger.getLogger("fr.inserm.um915.bdibd");
	int markerStart;
	int markerEnd;
	List<Integer> pairIndexes;
	boolean showIbd[]=new boolean[]{true,false,false};
	//float limit=0.1f;
	/* shall we use IBD0 or 1 or 2 ?*/
	private DataExtractorFactory datatypeFactory;
	private DataExtractor dataExtractor=null;
	
	LinkageTableModel(
			int markerStart,
			int markerEnd,
			List<Integer> selIndexes,
			DataExtractorFactory datatypeFactory
			)
		{
		this.markerStart=markerStart;
		this.markerEnd=markerEnd;
		this.pairIndexes=new Vector<Integer>(selIndexes);
		this.datatypeFactory=datatypeFactory;
		this.dataExtractor=this.datatypeFactory.createDataExtractor();

	
		WhichIBD ibd_index_012=this.datatypeFactory.getWichIBD();
		for(int i=0;i< showIbd.length;++i)
			{
			showIbd[i]=(i==ibd_index_012.ordinal());
			}
		this.dataExtractor.setPairOfIndividualIds(this.pairIndexes);
		//this.limit=(ibd_index_012==WhichIBD.USE_IBD_0?0.1f:0.9f);
		}
	
	int getColumnPerPair()
		{
		int n=0;
		for(boolean b:showIbd) n+=(b?1:0);
		return n;
		}
	
	private Model getModel()
		{
		return datatypeFactory.getModel();
		}
	
	private Transaction getTransaction()
		{
		return datatypeFactory.getTransaction();
		}

	ChromPosition getChromPosition(int rowIndex)
		{
		Marker m=getModel().getMarkerByIndex(getTransaction(), this.markerStart+rowIndex);
		return m.getPosition();
		}
	
	byte getChromosomeId(int rowIndex)
		{
		return getChromPosition(rowIndex).getChromosomeId();
		}
	
	int getPosition(int rowIndex)
		{
		return getChromPosition(rowIndex).getPosition();
		}
	
	Double getPairCount(int rowIndex)
		{
		return Double.class.cast(getValueAt(rowIndex,COUNT_COLUMN_LEFT-1));
		}
	
	Marker getMarker(int rowIndex)
		{
		return getModel().getMarkerByIndex(getTransaction(), this.markerStart+rowIndex);
		}
	
	LinkageRow getLinkageRow(int rowIndex)
		{
		Marker m=getMarker(rowIndex);
		if(m==null) return null;
		return getModel().getIBDByMarker(getTransaction(), m.getName());
		}
	
	@Override
	public Object getValueAt(int rowIndex, int columnIndex)
		{
		Marker m=getMarker(rowIndex);
		if(m==null) throw new NullPointerException();
		switch(columnIndex)
			{
			case 0: return m.getName();
			case 1: return m.getPosition().getChromosome();
			case 2: return m.getPosition().getPosition();
			case 3: 
				{
				LinkageRow statuses=getModel().getIBDByMarker(getTransaction(), m.getName());
				if(statuses==null)
					{
					LOG.info("no ibddata for "+m.getName());
					return null;
					}
				return this.dataExtractor.getValue(statuses);
				}
			default:break;
			}
		columnIndex-=COUNT_COLUMN_LEFT;
		int pair_index=columnIndex/getColumnPerPair();
		if(pair_index<this.pairIndexes.size())
			{
			IBDStatus status= getModel().getIbdStatus(
					getTransaction(),
					m.getName(),
					this.pairIndexes.get(pair_index)
					);
			int ibdcol=columnIndex%getColumnPerPair();
			int n=0;
			for(int i=0;i< showIbd.length;++i)
				{
				if(!showIbd[i]) continue;
				if(n==ibdcol) return status.getIbd(i);
				++n;
				}
			}
		return null;
		}
	
	
	
	@Override
	public String getColumnName(int columnIndex)
		{
		switch(columnIndex)
			{
			case 0: return "Marker";
			case 1: return "CHROM";
			case 2: return "POS";
			case 3: return "p.";
			default:break;
			}
		columnIndex-=COUNT_COLUMN_LEFT;
		int pair_index=columnIndex/getColumnPerPair();
		if(pair_index<this.pairIndexes.size())
			{
			PairOfIndividual p=getModel().getPairOfIndividualByPairId(getTransaction(), this.pairIndexes.get(pair_index));
			StringBuilder b=new StringBuilder();
			b.append(p.get(0).getFamily());
			b.append("|");
			b.append(p.get(0).getName());
			if(!p.get(0).getFamily().equalsIgnoreCase(p.get(1).getFamily()))
				{
				b.append("|");
				b.append(p.get(1).getFamily());
				}
			b.append("|");
			b.append(p.get(1).getName());
			
			
			int ibdcol=columnIndex%getColumnPerPair();
			int n=0;
			
			for(int ibd=0;ibd< showIbd.length;++ibd)
				{
				if(!showIbd[ibd]) continue;
				if(n==ibdcol)
					{
					b.append("IBD"+ibd);
					break;
					}
				++n;
				}
			return b.toString();
			}
		
		return "$"+(1+columnIndex);
		}
	
	@Override
	public Class<?> getColumnClass(int columnIndex)
		{
		switch(columnIndex)
			{
			case 0: return String.class;
			case 1: return String.class;
			case 2: return Integer.class;
			case 3: return Double.class;
			default: return Float.class;
			}
		}
	
	public int getSelectedPairCount()
		{
		return this.pairIndexes.size();
		}
	
	@Override
	public int getColumnCount()
		{
		return COUNT_COLUMN_LEFT+getSelectedPairCount()*getColumnPerPair();
		}
	
	@Override
	public int getRowCount()
		{
		return 1+(markerEnd-markerStart);
		}
	}



@SuppressWarnings("serial")
public class IBDFrame extends JFrame
	{
	private static final float DEFAULT_TRESHOLD_LIMIT=0.1f;
	private static final Logger LOG=Logger.getLogger("fr.inserm.um915.bdibd");
	private static final String DATA_EXTRACTOR_FACTORY="data.extractor.factory";
	private static final long serialVersionUID = 1L;
	private Transaction txn=null;
	private Model model;
	private JDesktopPane desktopPane;
	private PedigreeFrame pedigreeFrame;
	private PairOfIndividualFrame pairsFrame;
	private MarkersFrame markersFrame;
	private MetaFrame metaFrame;
	private ActionMap actionMap=new ActionMap();
	private double limit_portero_95=0.95;
	/* data extractors */
	private final DataExtractorFactoryIBDCount dataExtractorIBD0Count=new DataExtractorFactoryIBDCount(WhichIBD.USE_IBD_0,true);
	private final DataExtractorFactoryIBDCount dataExtractorIBD1Count=new DataExtractorFactoryIBDCount(WhichIBD.USE_IBD_1,true);
	private final DataExtractorFactoryIBDCount dataExtractorIBD2Count=new DataExtractorFactoryIBDCount(WhichIBD.USE_IBD_2,true);
	private final DataExtractorFactoryIBDCount dataExtractorIBD0Sum=new DataExtractorFactoryIBDCount(WhichIBD.USE_IBD_0,false);
	private final DataExtractorFactoryIBDCount dataExtractorIBD1Sum=new DataExtractorFactoryIBDCount(WhichIBD.USE_IBD_1,false);
	private final DataExtractorFactoryIBDCount dataExtractorIBD2Sum=new DataExtractorFactoryIBDCount(WhichIBD.USE_IBD_2,false);
	private final DataExtractorFactoryIKinship dataExtractorKinshipZscore= new DataExtractorFactoryIKinship(false);
	private final DataExtractorFactoryIKinship dataExtractorKinshipZscoreAA= new DataExtractorFactoryIKinship(true);
	private final DataExtractorFactory dataExtractorFactories[]=new DataExtractorFactory[]
	    {
		dataExtractorIBD0Count,
		dataExtractorIBD1Count,
		dataExtractorIBD2Count,
		dataExtractorIBD0Sum,
		dataExtractorIBD1Sum,
		dataExtractorIBD2Sum,
		dataExtractorKinshipZscore,
		dataExtractorKinshipZscoreAA
		};
	private ButtonGroup dataExtractorTypeButtonGroup;
	private JRadioButtonMenuItem dataExtractorTypeRadioButton[];
	/**
	 * MyInternalFrame
	 */
	class MyInternalFrame extends JInternalFrame
		{
		private String lastSearchQuery="";
		MyInternalFrame()
			{
			super("",true,false,true,true);
			JMenuBar bar=new JMenuBar();
			setJMenuBar(bar);
			JMenu menu=new JMenu("File");
			bar.add(menu);
			JPanel contentPane=new JPanel(new BorderLayout(5, 5));
			contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
			setContentPane(contentPane);
			}
		Model getModel()
			{
			return IBDFrame.this.getModel();
			}
		
		TableCellRenderer createTableCellRenderer()
			{
			return new MyTableCellRenderer();
			}
		
		void setTableRenderer(JTable t)
			{
			TableCellRenderer r=createTableCellRenderer();
			for(int i=0;i< t.getColumnCount();++i)
				{
				t.getColumnModel().getColumn(i).setCellRenderer(r);
				}
			}
		
		private void findInTable(JTable table,String query)
			{
			query=query.trim().toLowerCase();
			if(query.length()==0) return;
			int startRow=table.getSelectedRow();
			if(startRow==-1 || startRow>=table.getRowCount())
				{
				startRow=0;
				}
			
			int matchingRow=-1;
			if(table.getRowCount()>0)
				{
				TableModel tm=table.getModel();
				int currRow=startRow;
				for(;;)
					{
					currRow++;
					if(currRow>=table.getRowCount())
						{
						currRow=0;
						}
					int rM=table.convertRowIndexToModel(currRow);
					if(rM!=-1)
						{
						for(int c=0;c< tm.getColumnCount();++c)
							{
							String cell= String.valueOf(tm.getValueAt(rM, c)).toLowerCase();
							if(cell.indexOf(query)!=-1)
								{
								matchingRow=currRow;
								break;
								}
							}
						if(matchingRow!=-1) break;
						}
					if(currRow==startRow) break;
					}
				}
			if(matchingRow==-1)
				{
				JOptionPane.showMessageDialog(table, "Cannot find :"+query);
				return;
				}
			table.getSelectionModel().setSelectionInterval(matchingRow, matchingRow);
			table.scrollRectToVisible(table.getCellRect(matchingRow, 0, true));
			}
		
		void insertTableUtils(JTable table)
			{
			getJMenuBar().getMenu(0).insert(createSaveAsTable(table), 0);
			
			AbstractAction action=new AbstractAction("Find ...")
				{
				@Override
				public void actionPerformed(ActionEvent e)
					{
					JTable table=(JTable)getValue("component");
					String q=JOptionPane.showInputDialog(IBDFrame.this,"Find:",lastSearchQuery);
					if(q==null || q.trim().length()==0) return;
					MyInternalFrame.this.lastSearchQuery=q.trim();
					findInTable(table,MyInternalFrame.this.lastSearchQuery);
					}
				};
			action.putValue(AbstractAction.ACCELERATOR_KEY,KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_MASK));
			action.putValue("component", table);
			getJMenuBar().getMenu(0).insert(action, 0);
			
			action=new AbstractAction("Find Again...")
				{
				@Override
				public void actionPerformed(ActionEvent e)
					{
					JTable table=(JTable)getValue("component");
					findInTable(table,MyInternalFrame.this.lastSearchQuery);
					}
				};
			action.putValue("component", table);
			action.putValue(AbstractAction.ACCELERATOR_KEY,KeyStroke.getKeyStroke(KeyEvent.VK_G, InputEvent.CTRL_MASK));
			getJMenuBar().getMenu(0).insert(action, 0);
			}
		
		
		
		
		
		
		AbstractAction createSaveAsTable(JTable table)
			{
			AbstractAction action=new AbstractAction("Save Table As....")
				{
				@Override
				public void actionPerformed(ActionEvent e)
					{
					JTable table=(JTable)getValue("component");
					JFileChooser fc=new JFileChooser();
					if(fc.showSaveDialog(fc)!=JFileChooser.APPROVE_OPTION) return;
					File f=fc.getSelectedFile();
					if(f==null) return;
					if(f.exists() && JOptionPane.showConfirmDialog(table,"File exists. Overwrite ?","Overwrite ?",JOptionPane.OK_CANCEL_OPTION,JOptionPane.WARNING_MESSAGE,null)!=JOptionPane.OK_OPTION)
						{
						return;
						}
					try {
						TableModel m=table.getModel();
						BufferedWriter w=new BufferedWriter(new FileWriter(f));
						for(int j=0;j< m.getColumnCount();++j)
							{
							w.write(j==0?"#":"\t");
							w.write(m.getColumnName(j));
							}
						w.write("\n");
						for(int i=0;i< m.getRowCount();++i)
							{
							for(int j=0;j< m.getColumnCount();++j)
								{
								if(j>0) w.write("\t");
								w.write(String.valueOf(m.getValueAt(i,j)));
								}
							w.write("\n");
							}
						w.flush();
						w.close();
						}
					catch (Exception e2)
						{
						JOptionPane.showMessageDialog(table, e2.getMessage(),"Error",JOptionPane.ERROR_MESSAGE,null);
						}
					}
				};
			action.putValue("component", table);
			return action;
			}
		}
	
	class ChromStartEndFrame extends MyInternalFrame
		{
		AbstractChromStartEndTableModel tableModel;
		JTable table;
		
		ChromStartEndFrame(Transaction txn,
			String title,
			List<ChromStartEnd> data
			)
			{
			setTitle(title);
			setClosable(true);
			this.tableModel=new DefaultChromStartEndTableModel(data);
			this.table=new JTable(this.tableModel);
			this.table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
			getContentPane().add(new JScrollPane(this.table),BorderLayout.CENTER);
			insertTableUtils(this.table);
			setTableRenderer(this.table);
			
			AbstractAction action=new AbstractAction("Select Markers")
				{
				@Override
				public void actionPerformed(ActionEvent e)
					{
					int sel=ChromStartEndFrame.this.table.getSelectedRow();
					sel=ChromStartEndFrame.this.table.convertRowIndexToModel(sel);
					if(sel==-1) return;
					ChromStartEnd cse=ChromStartEndFrame.this.tableModel.getElementAt(sel);
					if(cse==null) return;
					IBDFrame.this.markersFrame.selectRange(cse);
					}
				};
			getActionMap().put("ACTION.SELECT.MARKERS", action);
			action.setEnabled(false);
			table.getSelectionModel().addListSelectionListener(new ListSelectionListener()
				{
				@Override
				public void valueChanged(ListSelectionEvent e)
					{
					ChromStartEndFrame.this.getActionMap().get("ACTION.SELECT.MARKERS").setEnabled(!ChromStartEndFrame.this.table.getSelectionModel().isSelectionEmpty());
					}
				});
			JPanel top=new JPanel(new FlowLayout(FlowLayout.LEADING));
			getContentPane().add(top,BorderLayout.NORTH);
			top.add(new JButton(action));
			getJMenuBar().getMenu(0).add(action);
			}
		/** when I put this frame as a choice in a JList */
		@Override
		public String toString()
			{
			return this.getTitle();
			}
		}
	
	/** pedigree frame */
	class PedigreeFrame extends MyInternalFrame
		{
		PedigreeTableModel tableModel;
		JTable table;
		PedigreeFrame(Transaction txn)
			{
			setTitle("Pedigree");
			setClosable(false);
			this.tableModel=new PedigreeTableModel(txn,
					IBDFrame.this.getModel(),
					IBDFrame.this.getModel().getIndividualCount()
					);
			this.table=new JTable(this.tableModel);
			this.table.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
			this.table.setShowVerticalLines(false);
			getContentPane().add(new JScrollPane(this.table),BorderLayout.CENTER);
			insertTableUtils(this.table);
			setTableRenderer(this.table);
			JPanel top=new JPanel(new FlowLayout(FlowLayout.LEADING));
			getContentPane().add(top,BorderLayout.NORTH);
			AbstractAction action=new AbstractAction("Draw Pedigree")
				{
				@Override
				public void actionPerformed(ActionEvent e) {
					newPedigreeWindow();
					}
				};
			getActionMap().put("ACTION.DRAW.PEDIGREE", action);
			action.setEnabled(false);
			getJMenuBar().getMenu(0).add(action);
			top.add(new JButton(action));
			this.table.getSelectionModel().addListSelectionListener(new ListSelectionListener()
				{
				@Override
				public void valueChanged(ListSelectionEvent e) {
					getActionMap().get("ACTION.DRAW.PEDIGREE").setEnabled(table.getSelectedRowCount()>0);
					}
				});
			}
		private void newPedigreeWindow()
			{
			int i=table.getSelectedRow();
			if(i==-1) return;
			Individual indi=this.tableModel.getElementAt(i);
			if(indi==null) return;
			FamilyTreeFrame f=new FamilyTreeFrame(txn, indi.getFamily());
			showInternalFrame(f);
			}
		}
	
	/** pedigree frame */
	class PairOfIndividualFrame extends MyInternalFrame
		{
		Transaction txn;
		PairOfIndividualsTableModel tableModel;
		JTable table;
		PairOfIndividualFrame(Transaction txn)
			{
			setTitle("Pairs");
			setClosable(false);
			this.txn=txn;
			this.tableModel=new PairOfIndividualsTableModel(txn,
					IBDFrame.this.getModel(),
					IBDFrame.this.getModel().getPairCount()
					);
			this.table=new JTable(this.tableModel);
			this.table.setShowVerticalLines(false);
			this.table.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
			getContentPane().add(new JScrollPane(this.table));
			insertTableUtils(this.table);
			setTableRenderer(this.table);
			
			JPanel top=new JPanel(new FlowLayout(FlowLayout.LEADING));
			getContentPane().add(top,BorderLayout.NORTH);
			AbstractAction action=new AbstractAction("Cleanup Self/Self")
				{
				@Override
				public void actionPerformed(ActionEvent e)
					{
					cleanupSelfSelf();
					}
				};
			top.add(new JButton(action));
			getJMenuBar().getMenu(0).add(action);
			}
		
		private void cleanupSelfSelf()
			{
			ListSelectionModel m=table.getSelectionModel();
			if(m.isSelectionEmpty()) return;
			List<Integer> set=new ArrayList<Integer>();
			for(int pairid=0;pairid< getModel().getPairCount();++pairid)
				{
				if(!m.isSelectedIndex(pairid)) continue;
				PairOfIndividual p=getModel().getPairOfIndividualByPairId(this.txn, pairid);
				if(p.isSelfSelf()) continue;
				set.add(pairid);
				}
			m.clearSelection();
			for(int pairid:set)
				{
				m.addSelectionInterval(pairid, pairid);
				}
			}
		
		List<Integer> getSelectedIndexes()
			{
			List<Integer> L=new Vector<Integer>();
			
			for(int i=0;i< tableModel.getRowCount();++i)
				{
				if(
						this.table.getSelectionModel().isSelectionEmpty() ||
						this.table.getSelectionModel().isSelectedIndex(i))
					{
					L.add(i);
					}
				}
				
			return L;
			}
		}
	/**
	 * MarkersFrame
	 */
	class MarkersFrame extends MyInternalFrame
		{
		Transaction txn;
		MarkerTableModel tableModel;
		JTable table;
		JTextField tfSelect;
		MarkersFrame(Transaction txn)
			{
			setClosable(false);
			setTitle("Markers");
			this.tableModel=new MarkerTableModel(txn,
					IBDFrame.this.getModel(),
					IBDFrame.this.getModel().getMarkerCount()
					);
			this.txn=txn;
			this.table=new JTable(this.tableModel);
			this.table.setShowVerticalLines(false);
			this.table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
			getContentPane().add(new JScrollPane(this.table));
			insertTableUtils(this.table);
			setTableRenderer(this.table);
			
			JPanel top=new JPanel(new FlowLayout(FlowLayout.LEADING));
			getContentPane().add(top,BorderLayout.NORTH);
			top.add(new JLabel("Select (chr:start-end):",JLabel.RIGHT));
			tfSelect=new JTextField(15);
			top.add(tfSelect);
			AbstractAction action=new AbstractAction("Select")
				{
				@Override
				public void actionPerformed(ActionEvent e)
					{
					selectRange();
					}
				};
			tfSelect.addActionListener(action);
			top.add(new JButton(action));
			}
		void selectRange()
			{
			String s= tfSelect.getText().trim();
			if(s.trim().length()==0) return;
			ChromStartEnd range=ChromStartEnd.parse(s);
			if(range==null)
				{
				JOptionPane.showMessageDialog(this, "Bad position:"+s);
				return;
				}
			selectRange(range);
			}
		void selectRange(ChromStartEnd range)
			{
			if(range==null) return;
			int i1= getModel().getMarkerIndexLowerBound(txn, new ChromPosition(range.getChromosomeId(),range.getStart()));
			int i2= getModel().getMarkerIndexLowerBound(txn, new ChromPosition(range.getChromosomeId(),range.getEnd()));
			if(i1>=getModel().getMarkerCount())
				{
				return;
				}
			if(i2>=getModel().getMarkerCount())
				{
				i2=getModel().getMarkerCount()-1;
				}
			else
				{
				i2--;
				}
			if(i1>i2) return; //uhh??
			table.getSelectionModel().clearSelection();
			table.getSelectionModel().setSelectionInterval(i1, i2);
			if(table.getSelectedRow()!=-1)
				{
				table.scrollRectToVisible(table.getCellRect(table.getSelectedRow(), 0, true));	
				}
			}
		}
	
	/**
	 * 
	 * ManhattanFrame
	 *
	 */
	class ManhattanFrame extends MyInternalFrame
		{
		private JTabbedPane tabbed;
		private ImageBuilder imageBuilder=null;
		
		/** drawing area in the manhattan */
		private class DrawingArea extends JPanel
			{
			private ImageBuilder.FamData family;
			private Point mouseStart=null;
			private Point mousePrev=null;
			private DataExtractorFactory dataExtractorFactory;
			
			DrawingArea(
					ImageBuilder.FamData family,
					DataExtractorFactory dataExtractorFactory
					)
				{
				super(true);
				this.dataExtractorFactory=dataExtractorFactory;
				this.family = family;
				this.setOpaque(false);
				this.setBackground(Color.LIGHT_GRAY);
				this.setForeground(Color.BLACK);
				this.setToolTipText("");
				Dimension dim=new Dimension(
					family.image.getWidth(),
					family.image.getHeight()
					);
				this.setPreferredSize(dim);
				this.setMinimumSize(dim);
				
				MouseAdapter listen=new MouseAdapter()
					{
					@Override
					public void mousePressed(MouseEvent e)
						{
						mouseStart=null;
						mousePrev=null;
						if(!getGenomeBounds().contains(e.getPoint())) return;
						mouseStart=new Point(e.getPoint());	
						}
					@Override
					public void mouseDragged(MouseEvent e)
						{
						if(mouseStart==null) return;
						Graphics2D g=Graphics2D.class.cast(DrawingArea.this.getGraphics());
						g.setXORMode(getBackground());
						if(mousePrev!=null)
							{
							g.fill(getSelectRect());
							}
						mousePrev=new Point(e.getPoint());
						g.fill(getSelectRect());
						g.setPaintMode();
						}
					@Override
					public void mouseReleased(MouseEvent e)
						{
						try
							{
							if(mouseStart==null) return;
							if(mousePrev!=null)
								{
								ChromPosition pos1;
								ChromPosition pos2;
								if(mouseStart.x< mousePrev.x)
									{
									pos1=convertToPosition(mouseStart.x);
									pos2=convertToPosition(mousePrev.x);
									}
								else
									{
									pos2=convertToPosition(mouseStart.x);
									pos1=convertToPosition(mousePrev.x);
									}
								if(pos1==null || pos2==null) return;
								int index1= getModel().getMarkerIndexLowerBound(txn, pos1);
								if(index1>= getModel().getMarkerCount()) return;
								int index2= getModel().getMarkerIndexLowerBound(txn, pos2);
								if(index2>= getModel().getMarkerCount())
									{
									index2=getModel().getMarkerCount()-1;
									}
								if(index1>index2) return;
								LinkageFrame frame=new LinkageFrame(
										index1,
										index2,
										new ArrayList<Integer>(DrawingArea.this.family.pairids),
										DrawingArea.this.dataExtractorFactory
										);
								showInternalFrame(frame);
								}
							}
						finally
							{
							repaint();
							}
						}
					};
					
				this.addMouseListener(listen);
				this.addMouseMotionListener(listen);
				}
			
			
			@Override
			public String getToolTipText(MouseEvent e)
				{
				if(!getGenomeBounds().contains(e.getPoint())) return null;
				ChromPosition pos= convertToPosition(e.getX());
				return pos==null?null:pos.toString();
				}
			
			@Override
			protected void paintComponent(Graphics g) {
				super.paintComponent(g);
				paintDrawingArea(Graphics2D.class.cast(g));
				}
			private Point getImageTopLeft()
				{
				Dimension d=this.getSize();
				return new Point(
					Math.max(0, (d.width - this.family.image.getWidth())/2),
					Math.min(0, (d.height - this.family.image.getHeight())/2)
					);
				}
			private Rectangle getGenomeBounds()
				{
				Point tl=getImageTopLeft();
				tl.x+=ManhattanFrame.this.imageBuilder.margin.left;
				tl.y+=ManhattanFrame.this.imageBuilder.margin.top;
				return new Rectangle(
					tl,
					new Dimension(
							ManhattanFrame.this.imageBuilder.frameDim.width,
							ManhattanFrame.this.imageBuilder.frameDim.height
							)	
					);
				}
			private Rectangle getSelectRect()
				{
				Rectangle r=getGenomeBounds();
				int newx= Math.max(r.x,Math.min(mousePrev.x, mouseStart.x));
				int maxx=Math.min(r.x+r.width,Math.max(mousePrev.x, mouseStart.x));
				r.x=newx;
				r.width=maxx-newx;
				return r;
				}
			
			private void paintDrawingArea(Graphics2D g)
				{
				Point p= getImageTopLeft();
				g.drawImage(
					this.family.image,
					p.x,
					p.y,
					null
					);
				}
			
			ChromPosition convertToPosition(int x)
				{
				Point p=getImageTopLeft();
				x-=p.x;
				for(ImageBuilder.ChromInfo ci:ManhattanFrame.this.imageBuilder.id2chrom.values())
					{
					if(!ci.bounds.contains(x, ci.bounds.y+1)) continue;
					int pos=(int)(ci.chromStart+((x-ci.bounds.x)/(double)ci.bounds.width)*ci.size());	
					return new ChromPosition(ci.chrom,pos);
					}
				return null;
				}
			}
		
		
		/** ManhattanFrame */
		ManhattanFrame(ImageBuilder imgBuilder)
			{
			setClosable(true);
			setTitle("Manhattan "+imgBuilder.getDataExtractorFactory().getDescription());
			setDefaultCloseOperation(DISPOSE_ON_CLOSE);
			this.tabbed=new JTabbedPane();
			this.imageBuilder=imgBuilder;
			getContentPane().add(tabbed,BorderLayout.CENTER);
			for(ImageBuilder.FamData famData:imgBuilder.fam2famData.values())
				{
				DrawingArea drawingArea=new DrawingArea(famData,imgBuilder.getDataExtractorFactory());
				JScrollPane scroll=new JScrollPane(drawingArea);
				this.tabbed.addTab(famData.family.toString(), scroll);
				}
			AbstractAction action=new AbstractAction("Save Images as...")
				{
				@Override
				public void actionPerformed(ActionEvent e)
					{
					doMenuSaveImagesAs();
					}
				};
			JPanel top=new JPanel(new FlowLayout(FlowLayout.LEADING));
			getContentPane().add(top,BorderLayout.NORTH);
			top.add(new JButton(action));
			getJMenuBar().getMenu(0).add(action);
			}
		
		private void doMenuSaveImagesAs()
			{
			try {
				if(this.imageBuilder.fam2famData.isEmpty()) return;
				JFileChooser fc=new JFileChooser();
				if(fc.showSaveDialog(null)!=JFileChooser.APPROVE_OPTION) return ;
				File f=fc.getSelectedFile();
				if(f==null) return;
				if(this.imageBuilder.fam2famData.size()==1)
					{
					if(!(f.getName().toLowerCase().endsWith(".png") ))
						{
						f=new File(f.toString()+".png");
						}
					}
				else
					{
					if(!(f.getName().toLowerCase().endsWith(".zip") ))
						{
						f=new File(f.toString()+".zip");
						}
					}
				if(f.exists() && JOptionPane.showConfirmDialog(this,"File "+f+" exists. Overwrite ?","Overwrite ?",JOptionPane.OK_CANCEL_OPTION,JOptionPane.WARNING_MESSAGE,null)!=JOptionPane.OK_OPTION)
					{
					return;
					}
				this.imageBuilder.saveAs(f);
				} 
			catch (Exception e)
				{
				JOptionPane.showMessageDialog(this,"Error:"+e.getMessage());
				}
			}
		}
	
	/**
	 * LinkageFrame
	 *
	 */
	class LinkageFrame extends MyInternalFrame
		{
		LinkageTableModel tableModel;
		JTable table;
		ManhattanThread thread=null;
		JProgressBar progress=null;
		//data extractor factory
		DataExtractorFactory dataExtractorFactory;
		
		/** thread creating manhatan plot */
		class ManhattanThread extends Thread
			{
			ImageBuilder ib=new ImageBuilder();
			
			ManhattanThread(List<ChromStartEnd> regionsHighligth)
				{
				if(regionsHighligth!=null)
					{
					this.ib.regionsHighLight=Collections.unmodifiableList(regionsHighligth);
					}
				}
			
			@Override
			public void run()
				{
				
				try {
					safeInvoke(new Runnable()
						{
						@Override
						public void run()
							{
							LinkageFrame.this.progress.setIndeterminate(true);
							}
						});
					//ib.limit=tableModel.limit;
					ib.progress=LinkageFrame.this.progress;
			
					ib.build(
						(DataExtractorFactory)LinkageFrame.this.dataExtractorFactory.clone(),
						getModel().getMarkerByIndex(txn, tableModel.markerStart).getPosition(),
						getModel().getMarkerByIndex(txn, tableModel.markerEnd).getPosition(),
						new HashSet<Integer>(tableModel.pairIndexes)
						);
					if(LinkageFrame.this.thread!=this) return;
					safeInvoke(new Runnable()
						{
						@Override
						public void run()
							{
							ManhattanFrame f=new ManhattanFrame(ib);
							showInternalFrame(f);
							}
						});
					
					ib=null;
					}
				catch (Throwable err)
					{
					ib=null;
					final String msg="Error:"+err.getMessage();
					safeInvoke(new Runnable()
							{
							@Override
							public void run() {
								JOptionPane.showMessageDialog(LinkageFrame.this,msg);
								}
							});
					}
				finally
					{
					ib=null;
					safeInvoke(new Runnable()
						{
						@Override
						public void run() {
							LinkageFrame.this.thread=null;
							LinkageFrame.this.progress.setIndeterminate(false);
							LinkageFrame.this.getActionMap().get("MAKE.MANHATTAN").setEnabled(true);
							}
						});
						
					}
				}
			
			void safeInvoke(Runnable runner)
				{
				try
					{
					SwingUtilities.invokeAndWait(runner);
					}
				catch(Exception err2)
					{
					err2.printStackTrace();
					}
				}
			}
		
		class LinkageRenderer extends MyTableCellRenderer
			{
			private Color COLOR1=new Color(51,255,204);
			private Color COLOR2=new Color(204,153,153);
			//private WhichIBD show_ibd_012=WhichIBD.USE_IBD_0;
			private int column_to_highlight=0;
			private DataExtractorFactory dataExtractorFactory;
			LinkageRenderer(DataExtractorFactory dataExtractorFactory)
				{
				this.dataExtractorFactory=dataExtractorFactory;
				
				int n=0;
				for(int i=0;i< tableModel.showIbd.length;++i)
					{
					if(!tableModel.showIbd[i]) continue;
					if(i==dataExtractorFactory.getWichIBD().ordinal()) column_to_highlight=n;
					n++;
					}
				}
			
			@Override
			public Component getTableCellRendererComponent(JTable table,
					Object value, boolean isSelected, boolean hasFocus,
					int row, int column) {
				Component c= super.getTableCellRendererComponent(table, value, isSelected, hasFocus,
						row, column);
				if(!tableModel.showIbd[this.dataExtractorFactory.getWichIBD().ordinal()])
					{
					return c;
					}
				if(isSelected || hasFocus || value==null || value.getClass()!=Float.class) return c;
				column=table.convertColumnIndexToModel(column);
				if(column==-1 || column< LinkageTableModel.COUNT_COLUMN_LEFT )
					{
					return c;
					}
				column-=LinkageTableModel.COUNT_COLUMN_LEFT;
				if(column%tableModel.getColumnPerPair()!=column_to_highlight)
					{
					return c;
					}
				
				Float v=Float.class.cast(value);
				if(this.dataExtractorFactory.getWichIBD()==WhichIBD.USE_IBD_0)
					{
					c.setBackground(v<this.dataExtractorFactory.getTreshold()?COLOR1:COLOR2);
					}
				else
					{
					c.setBackground(v>=this.dataExtractorFactory.getTreshold()?COLOR1:COLOR2);
					}
				return c;
				}
			}
		
		
		LinkageFrame(
				int markerStart,
				int markerEnd,
				List<Integer> selIndexes,
				DataExtractorFactory dataExtractorFactory
				)
			{
			setClosable(true);
			setTitle("Linkage "+(1+markerEnd-markerStart)+ " Markers x "+selIndexes.size()+" Pairs. "+dataExtractorFactory.getName());
			setDefaultCloseOperation(DISPOSE_ON_CLOSE);

			this.dataExtractorFactory=(DataExtractorFactory)dataExtractorFactory.clone();
			this.tableModel=new LinkageTableModel(
					markerStart,
					markerEnd,
					selIndexes,
					this.dataExtractorFactory
					);
			this.table=new JTable(this.tableModel);
			this.table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
			this.table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
			getContentPane().add(new JScrollPane(this.table));
			insertTableUtils(this.table);
			setTableRenderer(this.table);
			JPanel top=new JPanel(new FlowLayout(FlowLayout.LEADING));
			getContentPane().add(top,BorderLayout.NORTH);
			for(int i=0;i< 3;++i)
				{
				JToggleButton but=new JToggleButton("IBD"+i,tableModel.showIbd[i]);
				but.addActionListener(createIBdAction(i));
				top.add(but);
				}
			
			
			AbstractAction action=new AbstractAction("Manhattan")
				{
				@Override
				public void actionPerformed(ActionEvent e)
					{
					makeMahattan();
					}
				};
			getActionMap().put("MAKE.MANHATTAN", action);
			getJMenuBar().getMenu(0).add(action);
			top.add(new JButton(action));
			
			this.progress=new JProgressBar();
			this.progress.setPreferredSize(new Dimension(200,20));
			this.progress.setStringPainted(true);
			top.add(this.progress);
			
			top.add(new JSeparator(JSeparator.VERTICAL));
			top.add(new JLabel("Range Limit:",JLabel.RIGHT));
			JSpinner spin=new JSpinner(new SpinnerNumberModel(0.0,0.0,1E6,1.0));
			top.add(spin);
			action=new AbstractAction("Range (Wait!)")
				{
				@Override
				public void actionPerformed(ActionEvent e)
					{
					SpinnerNumberModel m=SpinnerNumberModel.class.cast(getValue("RANGE.SPIN.MODEL"));
					makeRangeWindow(m.getNumber().intValue());
					}
				};
			action.putValue("RANGE.SPIN.MODEL", spin.getModel());
			top.add(new JButton(action));
			getJMenuBar().getMenu(0).add(action);
			
			action=new AbstractAction("Portero 95")
				{
				@Override
				public void actionPerformed(ActionEvent e)
					{
					
					int modifiers = e.getModifiers();
					if((modifiers & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK)
						{	
						SpinnerNumberModel mvp=new SpinnerNumberModel(IBDFrame.this.limit_portero_95,0.0,1.0,0.001);
						JSpinner spin=new JSpinner(mvp);
						JSpinner.NumberEditor ed=new JSpinner.NumberEditor(spin);
						spin.setEditor(ed);
						spin.setPreferredSize(new Dimension(100,20));
						if(JOptionPane.showConfirmDialog(
								LinkageFrame.this, spin,
								"Select limit.",
								JOptionPane.OK_CANCEL_OPTION
								)!=JOptionPane.OK_OPTION)
							{
							return;
							}
						IBDFrame.this.limit_portero_95=mvp.getNumber().doubleValue();
						}
					doPortero95((float)limit_portero_95);
					}
				};
			
			top.add(new JButton(action));
			getJMenuBar().getMenu(0).add(action);
			getActionMap().put("ACTION.PORTERO.95", action);
			action.setEnabled(false);
			this.table.getSelectionModel().addListSelectionListener(new ListSelectionListener()
				{
				@Override
				public void valueChanged(ListSelectionEvent e) {
					getActionMap().get("ACTION.PORTERO.95").setEnabled(table.getSelectedRowCount()==1);
					}
				});
			
			
			this.addInternalFrameListener(new InternalFrameAdapter()
				{
				@Override
				public void internalFrameClosed(InternalFrameEvent e) {
					stopManhattan();
					}
				});
			}
		
		private void doPortero95(float threshold_probability)
			{
			if(table.getSelectedColumnCount()!=1) return;
			int r0=table.getSelectedRow();
			if(r0==-1) return;
			r0=table.convertRowIndexToModel(r0);
			if(r0==-1) return;
			ChromPosition p0= tableModel.getChromPosition(r0);
			LinkageRow row0=tableModel.getLinkageRow(r0);
			if(row0==null) return;
			LinkageRow.Stats stat0= row0.getStatistics(
					this.dataExtractorFactory.getTreshold(),
					tableModel.pairIndexes,
					this.dataExtractorFactory.getWichIBD()
					);
			if(stat0.getCount()!=tableModel.pairIndexes.size())
				{
				JOptionPane.showMessageDialog(this, "ALL pairs below limit: false");
				return;
				}
		
			/* scan backwards */
			ChromPosition prevPosition=p0;
			int i=r0;
			boolean prev_was_bad=false;
			while(i>0)
				{
				ChromPosition x= tableModel.getChromPosition(i);
				//not the same chrom, break
				if(x.getChromosomeId()!=p0.getChromosomeId()) break;
				LinkageRow rowprev=tableModel.getLinkageRow(i);
				LinkageRow.Stats statprev= rowprev.getStatistics(
						this.dataExtractorFactory.getTreshold(),
						tableModel.pairIndexes,
						this.dataExtractorFactory.getWichIBD()
						);
				//ALL is below limit, continue
				if(statprev.getCount() == tableModel.pairIndexes.size())
					{
					--i;
					prevPosition=x;
					continue;
					}
				
				boolean found95=false;
				for(Integer pairindex:tableModel.pairIndexes)
					{
					float f=rowprev.getIbd(pairindex,this.dataExtractorFactory.getWichIBD());
					if(f< threshold_probability) continue;
					found95=true;
					break;
					}
				
				if(found95)
					{
					if(prev_was_bad) break;
					prev_was_bad=true;
					}
				else
					{
					prev_was_bad=false;
					}
				prevPosition=x;
				--i;
				}
			/* scan forwards */
			ChromPosition nextPosition=p0;
			i=r0;
			prev_was_bad=true;
			while(i<tableModel.getRowCount())
				{
				ChromPosition x= tableModel.getChromPosition(i);
				//not the same chrom, break
				if(x.getChromosomeId()!=p0.getChromosomeId()) break;
				LinkageRow rownext=tableModel.getLinkageRow(i);
				LinkageRow.Stats statnext= rownext.getStatistics(
						this.dataExtractorFactory.getTreshold(),
						tableModel.pairIndexes,
						this.dataExtractorFactory.getWichIBD()
						);
				//ALL is below limit, continue
				if(statnext.getCount() == tableModel.pairIndexes.size())
					{
					++i;
					nextPosition=x;
					continue;
					}
				
				boolean found95=false;
				for(Integer pairindex:tableModel.pairIndexes)
					{
					float f=rownext.getIbd(pairindex,this.dataExtractorFactory.getWichIBD());
					if(f< threshold_probability ) continue;
					found95=true;
					break;
					}
				
				if(found95)
					{
					if(prev_was_bad) break;
					prev_was_bad=true;
					}
				else
					{
					prev_was_bad=false;
					}
				nextPosition=x;
				++i;
				}
			ChromStartEnd range=new ChromStartEnd(
					p0.getChromosomeId(),
					prevPosition.getPosition(),
					nextPosition.getPosition()
					);
			JOptionPane.showMessageDialog(this, new JTextField(range.toString(), 20));
			}
		private void stopManhattan()
			{
			if(this.thread!=null)
				{
				try { this.thread.interrupt(); }
				catch( Exception err) {} 
				}
			this.thread=null;
			getActionMap().get("MAKE.MANHATTAN").setEnabled(true);
			}
		/** make manhattan plot */
		private void makeMahattan()
			{
			stopManhattan();
			getActionMap().get("MAKE.MANHATTAN").setEnabled(false);
			
			//regions to be highlighted
			List<ChromStartEnd> regionsHighlight=null;
			
			//collect collections of chromStartEnd
			Vector<ChromStartEndFrame> l1=new Vector<IBDFrame.ChromStartEndFrame>();
			
			for(JInternalFrame jif: IBDFrame.this.desktopPane.getAllFrames())
				{
				if(jif.getClass()!=ChromStartEndFrame.class) continue;
				ChromStartEndFrame csef=ChromStartEndFrame.class.cast(jif);
				l1.add(csef);
				}
			if(!l1.isEmpty())
				{
				JList choice=new JList(l1);
				choice.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
				if(JOptionPane.showConfirmDialog(this,new JScrollPane(choice))!=JOptionPane.OK_OPTION) return;
				if(!choice.isSelectionEmpty())
					{
					ChromStartEndFrame csef=(ChromStartEndFrame)choice.getSelectedValue();
					regionsHighlight=csef.tableModel.asList();
					}
				}
			
			
			this.thread=new ManhattanThread(regionsHighlight);
			this.thread.start();
			
			}
		
		public AbstractAction createIBdAction(int ibd)
			{
			AbstractAction action=new AbstractAction("Show IBD"+ibd)
				{
				@Override
				public void actionPerformed(ActionEvent e)
					{
					int ibd=(Integer)getValue("IBD.STATUS");
					LinkageFrame.this.tableModel.showIbd[ibd]=JToggleButton.class.cast(e.getSource()).isSelected();
					LinkageFrame.this.tableModel.fireTableStructureChanged();
					setTableRenderer(LinkageFrame.this.table);
					}
				};
			action.putValue("IBD.STATUS", ibd);
			
			return action;
			}
		
		private void makeRangeWindow(double  limit)
			{
			try
				{
				List<RangeSegment>  L=new ArrayList<RangeSegment>(1000);
				for(int i=0;i< tableModel.getRowCount();++i)
					{
					byte chrom=tableModel.getChromosomeId(i);
					Double v=tableModel.getPairCount(i);
					if(v==null) continue;
					boolean below= v< limit;
					
					if(	L.isEmpty() ||
						L.get(L.size()-1).chrom!=chrom ||
						L.get(L.size()-1).below!=below
						)
					    {
						RangeSegment range=new RangeSegment();
						range.chrom=chrom;
					    range.below=below;
					    range.chromStart=tableModel.getPosition(i);
					    range.chromEnd=range.chromStart;
					    range.total=v;
					    range.count=1;
					    L.add(range);
					    }
					else
					    {
						RangeSegment last=L.get(L.size()-1);
					    last.chromEnd=tableModel.getPosition(i);
					    last.total+=v;
					    last.count++;
					    }
					}
				List<RangeSegment>  L2=new ArrayList<RangeSegment>(L.size());
				for(int i=0;i< L.size();i++)
					{
					int B,E;
					final RangeSegment r=L.get(i);
				    if(i==0 || r.chrom!=L.get(i-1).chrom)
						{
						B=0;
						}
				    else
						{
						B=L.get(i-1).chromEnd+1;
						}
				    if(i+1==L.size() ||  r.chrom!=L.get(i+1).chrom)
						{
						E= 300000000;//size hum chrom1
						}
				    else
						{
						E=L.get(i+1).chromStart-1;
						}
				    RangeSegment s=new RangeSegment();
				    s.chrom=r.chrom;
				    s.chromEnd=r.chromEnd;
				    s.chromStart=B;
				    s.chromEnd=E;
				    s.below=r.below;
				    s.count=r.count;
				    s.total=r.total;
				    L2.add(s);
					}
				RangeSegmentFrame rsf=new RangeSegmentFrame(L2);
				showInternalFrame(rsf);
				}
			catch(Throwable err)
				{
				err.printStackTrace();
				JOptionPane.showMessageDialog(this, "Error:"+err.getMessage());
				}
			}
		
		
		@Override
		TableCellRenderer createTableCellRenderer()
			{
			return new LinkageRenderer(this.dataExtractorFactory);
			}
		}

	/***************************************************************************************
	 * 
	 *  drawing pedigree frame
	 *  
	 *   */
	class FamilyTreeFrame extends MyInternalFrame
		{
		
		//private String familyName;
		private JPanel drawingArea;
		private Map<String,Individual> name2individuals=new HashMap<String, Individual>();
		private final int SIZE=72;
		private Set<Integer> selectedids=new HashSet<Integer>();
		
		/* CTOR */
		FamilyTreeFrame(
			Transaction txn,
			String familyName
			)
			{
			setClosable(true);
			setTitle("Family:"+familyName);
			//this.familyName=familyName;
			this.drawingArea=new JPanel(true)
				{
				protected void paintComponent(Graphics g)
					{
					super.paintComponent(g);
					paintDrawingArea(Graphics2D.class.cast(g));
					};
				};
			this.drawingArea.setOpaque(true);
			this.drawingArea.setBackground(Color.WHITE);
			this.drawingArea.setForeground(Color.BLACK);
			this.drawingArea.addMouseListener(new MouseAdapter()
				{
				@Override
				public void mousePressed(MouseEvent e)
					{
					Individual indi=findIndividualAt(e.getX(), e.getY());
					if(indi==null)
						{
						if(!e.isShiftDown()) selectedids.clear();
						}
					else if(e.isShiftDown())
						{
						if(selectedids.contains(indi.getId()))
							{
							selectedids.remove(indi.getId());
							}
						else
							{
							selectedids.add(indi.getId());
							}
						}
					else//indi!=null && !shift
						{
						selectedids.clear();
						selectedids.add(indi.getId());
						}
					//update table pedigree
					ListSelectionModel m=IBDFrame.this.pedigreeFrame.table.getSelectionModel();
					m.clearSelection();
					for(Integer i:selectedids)
						{
						m.addSelectionInterval(i, i);
						}
					drawingArea.repaint();
					}
				});

			int minx=Integer.MAX_VALUE;
			int miny=Integer.MAX_VALUE;
			int maxx=Integer.MIN_VALUE;
			int maxy=Integer.MIN_VALUE;
			
			for(Individual indi:getModel().getIndividualsByFamily(txn, familyName))
				{
				name2individuals.put(indi.getName(),indi);
				miny=Math.min(miny, indi.pedPt.y);
				maxy=Math.max(maxy, indi.pedPt.y);
				minx=Math.min(minx, indi.pedPt.x);
				maxx=Math.max(maxx, indi.pedPt.x);
				}
			Insets insets=new Insets(SIZE, SIZE, SIZE, SIZE);
			//flip y axis
			for(Individual indi:getModel().getIndividualsByFamily(txn, familyName))
				{
				indi.pedPt.y=(maxy-miny)-indi.pedPt.y;
				indi.pedPt.x+=insets.left;
				indi.pedPt.y+=insets.top;
				}
			
			
			
			
			minx-=insets.left;
			maxx+=insets.right;
			maxy+=insets.bottom;
			miny-=insets.top;
			
			Dimension dim=new Dimension(maxx-minx,maxy-miny);
			this.drawingArea.setPreferredSize(dim);
			this.drawingArea.setMinimumSize(dim);
			this.getContentPane().add(new JScrollPane(this.drawingArea));
			
			
			AbstractAction action=new AbstractAction("Save picture as....")
				{
				@Override
				public void actionPerformed(ActionEvent e) {
					doSaveImageAs();
					}
				};
			getJMenuBar().getMenu(0).add(action);
			}
		
		/* save image in a file */
		private void doSaveImageAs()
			{
			JFileChooser fc=new JFileChooser();
			if(fc.showSaveDialog(fc)!=JFileChooser.APPROVE_OPTION) return;
			File f=fc.getSelectedFile();
			if(f==null) return;
			if(!(f.getName().toLowerCase().endsWith(".png")))
				{
				f=new File(f.toString()+".png");
				}
			
			if(f.exists() && JOptionPane.showConfirmDialog(this,"File exists. Overwrite ?","Overwrite ?",JOptionPane.OK_CANCEL_OPTION,JOptionPane.WARNING_MESSAGE,null)!=JOptionPane.OK_OPTION)
				{
				return;
				}
			
			try {
				BufferedImage img=new BufferedImage(
						drawingArea.getWidth(),
						drawingArea.getHeight(),
						BufferedImage.TYPE_INT_RGB);
				Graphics2D g=img.createGraphics();
				g.setColor(Color.WHITE);
				g.fillRect(0, 0,
						drawingArea.getWidth(),
						drawingArea.getHeight());
				paintDrawingArea(g);
				g.dispose();
				ImageIO.write(img, "png", f);
				}
			catch (Exception e)
				{
				JOptionPane.showMessageDialog(this,"Error:"+ e.getMessage());
				}
			}
		
		private void paintDrawingArea(Graphics2D g)
			{
			g.setFont(g.getFont().deriveFont(24f));
			for(Individual indi:this.name2individuals.values())
				{
				paintLinks(g,indi);
				}
			for(Individual indi:this.name2individuals.values())
				{
				paintNode(g,indi);
				}
			}
		private void paintLinks(Graphics2D g,Individual indi)
			{
			Individual father=null;
			if(indi.hasFather())
				{
				father=name2individuals.get(indi.getFatherId());
				}
			Individual mother=null;
			if(indi.hasMother())
				{
				mother=name2individuals.get(indi.getMotherId());
				}
			GeneralPath path=new GeneralPath();
			
			g.setColor(Color.BLACK);
			
			if(father!=null && mother!=null)
	            {
	            int topY=(father.getY() + mother.getY())/2;
	            int topX=(father.getX() + mother.getX())/2;
	            int h=indi.getY()-topY;
	            
	            int Y1=topY+h/3;
	            int Y2=topY+(int)(h*2.0/3.0);
	            
	            path.moveTo(indi.getX(),indi.getY());
	            path.lineTo(indi.getX(),Y2);
	            path.lineTo(topX,Y2);
	            path.lineTo(topX,Y1);
	            
	          
	            path.lineTo(father.getX(),Y1);
	            path.lineTo(father.getX(),father.getY());
	            
	            
	            path.moveTo(topX,Y1);
	            path.lineTo(mother.getX(),Y1);
	            path.lineTo(mother.getX(),mother.getY());
	           
	            
	           }
			else if(father!=null)
	            {
	            int midY= (father.getY()+indi.getY())/2;
	            path.moveTo(indi.getX(),indi.getY());
	            path.lineTo(indi.getX(),midY);
	            path.lineTo(father.getX(),midY);
	            path.lineTo(father.getX(),father.getY());
	            }
			else if(mother!=null)
	            {
	            
	            int midY= (mother.getY()+indi.getY())/2;
	            path.moveTo(indi.getX(),indi.getY());
	            path.lineTo(indi.getX(),midY);
	            path.lineTo(mother.getX(),midY);
	            path.lineTo(mother.getX(),mother.getY());
	            }
			else
				{
				return;
				}
			Stroke old=g.getStroke();
			
			
			g.setStroke(new BasicStroke(2.0f));
			g.draw(path);
			g.setStroke(old);
			}
		
		Shape getShape(Individual indi)
			{
			if(indi.getGender()==1)
				{
				return new Rectangle(
					indi.getX()-SIZE/2,
					indi.getY()-SIZE/2,
					SIZE,
					SIZE
					);
				}
			else if(indi.getGender()==2)
				{
				return new Ellipse2D.Double(
						indi.getX()-SIZE/2,
						indi.getY()-SIZE/2,
						SIZE,
						SIZE
						);
				}
			else
				{
				return new Rectangle(indi.getX()-1,indi.getY()-1,3,3);
				}
			}
		
		private void paintNode(Graphics2D g,Individual indi)
			{
			Shape shape=getShape(indi);
			Color bckg=Color.RED;
			switch(indi.getStatus())
				{
				case 1: bckg=Color.WHITE; break;
				case 2: bckg=Color.RED; break;
				case -9: bckg=Color.GRAY;break;
				default:break;
				}
			g.setColor(bckg);
			g.fill(shape);
			
			Stroke old=g.getStroke();
			g.setStroke(new BasicStroke(3f));
			if(selectedids.contains(indi.getId()))
				{
				
				g.setColor(Color.GREEN);
				g.draw(shape);
				
				}
			else
				{
				g.setColor(Color.BLACK);
				g.draw(shape);
				}
			g.setStroke(old);
			
			

			 String name= indi.getName();
			 FontMetrics fm=g.getFontMetrics();
             int w = fm.stringWidth(name) + 10;
             int h = fm.getHeight() + 4;
             int halfy=0;
             g.setColor(Color.YELLOW);
             g.fillRect((int)indi.getX() - w/2, (int)indi.getY()+halfy - h / 2, w, h);
             g.setColor(Color.black);
             g.drawRect((int)indi.getX() - w/2, (int)indi.getY()+halfy - h / 2, w-1, h-1);
             g.drawString(name,(int)indi.getX() - (w-10)/2, (int)(indi.getY()+halfy - (h-4)/2) +fm.getAscent());
			}
		private Individual findIndividualAt(int x,int y)
			{
			for(Individual indi:this.name2individuals.values())
				{
				if(getShape(indi).contains(x, y)) return indi;
				}
			return null;
			}
		}
	
	
	/**
	 * 
	 * MetaFrame
	 *
	 */
	class MetaFrame extends MyInternalFrame
		{
		TableModel tableModel;
		JTable table;
		 MetaFrame(Transaction txn)
			{
			setClosable(false);
			setTitle("Meta");
			Map<String,String> prop=getModel().getMeta(txn);
			Vector<Vector<String>> L=new Vector<Vector<String>>(prop.size());
			for(Object k:prop.keySet())
				{
				Vector<String> r=new Vector<String>(2);
				r.add(k.toString());
				r.add(prop.get(k.toString()));
				L.add(r);
				}
			this.tableModel=new DefaultTableModel(
				L,
				new Vector<String>() {{ add("Key"); add("Value");}}
				)
				{
				public boolean isCellEditable(int row, int column) {return false;};
				};
			this.table=new JTable(this.tableModel);
			this.table.setFont(table.getFont().deriveFont(12.0f));
			this.table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
			getContentPane().add(new JScrollPane(this.table));
			insertTableUtils(this.table);
			setTableRenderer(this.table);
			}
		}
	
	/**
	 * 
	 * RangeSegmentFrame
	 *
	 */
	class RangeSegmentFrame extends MyInternalFrame
		{
		RangeSegmentTableModel tableModel;
		JTable table;
		RangeSegmentFrame(List<RangeSegment> segs)
			{
			setClosable(true);
			setTitle("Ranges");
			this.tableModel=new RangeSegmentTableModel(segs);
			this.table=new JTable(this.tableModel);
			this.table.setFont(table.getFont().deriveFont(12.0f));
			this.table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
			this.table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
			getContentPane().add(new JScrollPane(this.table));
			insertTableUtils(this.table);
			setTableRenderer(this.table);
			}
		}
	
	
	public IBDFrame(Transaction txn,Model model) throws HeadlessException
		{
		super("IBDFrame");
		AbstractAction action;
		this.txn=txn;
		this.model=model;
		this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
		this.addWindowListener(new WindowAdapter()
			{
			@Override
			public void windowClosing(WindowEvent e)
				{
				doMenuClose();
				}
			@Override
			public void windowClosed(WindowEvent e)
				{
				IBDFrame.this.model.close();
				}
			});
		this.addWindowListener(new WindowAdapter()
			{
			@Override
			public void windowOpened(WindowEvent e)
				{
				showInternalFrame(metaFrame);
				showInternalFrame(pedigreeFrame);
				showInternalFrame(pairsFrame);
				showInternalFrame(markersFrame);
				removeWindowListener(this);
				}
			});
		JMenuBar bar=new JMenuBar();
		setJMenuBar(bar);
		JMenu menu=new JMenu("File");
		bar.add(menu);
		menu.add(new AbstractAction("About")
			{
			@Override
			public void actionPerformed(ActionEvent e)
				{
				JOptionPane.showMessageDialog(IBDFrame.this,
					"<html><body><h1>IBDFrame</h1><h3>Pierre Lindenbaum PhD.</h3><h4>plindenbaum@yahoo.fr</h4></body></html>");
				}
			});
		
		action=new AbstractAction("Load BED data")
			{
			@Override
			public void actionPerformed(ActionEvent e)
				{
				doMenuLoadBedData();
				}
			};
		this.actionMap.put("LOAD.BED.DATA", action);
		menu.add(action);
		
		menu.add(new AbstractAction("Quit")
			{
			@Override
			public void actionPerformed(ActionEvent e)
				{
				doMenuClose();
				}
			});
		menu=new JMenu("IBD");
		bar.add(menu);
		action=new AbstractAction("Create View")
			{
			@Override
			public void actionPerformed(ActionEvent e)
				{
				doMenuCreateView();
				}
			};
		this.actionMap.put("IBD.CREATE.VIEW", action);
		menu.add(action);
		//asked by vincent choose between ibd 0/1/2
		this.dataExtractorTypeButtonGroup = new ButtonGroup();
		this.dataExtractorTypeRadioButton=new JRadioButtonMenuItem[this.dataExtractorFactories.length];
		for(int k=0;k< this.dataExtractorFactories.length;++k)
			{
			this.dataExtractorFactories[k].setTransaction(txn);
			this.dataExtractorFactories[k].setModel(model);
			this.dataExtractorFactories[k].setTreshold(DEFAULT_TRESHOLD_LIMIT);
			this.dataExtractorTypeRadioButton[k]=new JRadioButtonMenuItem(this.dataExtractorFactories[k].getName());
			this.dataExtractorTypeRadioButton[k].putClientProperty(DATA_EXTRACTOR_FACTORY, this.dataExtractorFactories[k]);
			menu.add(this.dataExtractorTypeRadioButton[k]);
			this.dataExtractorTypeButtonGroup.add(this.dataExtractorTypeRadioButton[k]);
			}
		this.dataExtractorTypeButtonGroup.setSelected(this.dataExtractorTypeRadioButton[0].getModel(),true);
		
		JPanel contentPane=new JPanel(new BorderLayout(5, 5));
		setContentPane(contentPane);
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		JToolBar toolBar=new JToolBar();
		contentPane.add(toolBar,BorderLayout.NORTH);
		toolBar.add(actionMap.get("IBD.CREATE.VIEW"));
		toolBar.add(actionMap.get("LOAD.BED.DATA"));
		this.desktopPane=new JDesktopPane();
		contentPane.add(this.desktopPane,BorderLayout.CENTER);
		this.metaFrame=new MetaFrame(txn);
		this.pedigreeFrame=new PedigreeFrame(txn);
		this.pairsFrame=new PairOfIndividualFrame(txn);
		this.markersFrame=new MarkersFrame(txn);
		/** when one or more individual is selected we select the pairs for those individuals */
		this.pedigreeFrame.table.getSelectionModel().addListSelectionListener(new ListSelectionListener()
			{
			@Override
			public void valueChanged(ListSelectionEvent e)
				{
				ListSelectionModel sel=pedigreeFrame.table.getSelectionModel();
				ListSelectionModel selPair=pairsFrame.table.getSelectionModel();
				selPair.clearSelection();
				//loop over each row=pair in pairsFrame
				for(int pairid=0;
					pairid< pairsFrame.table.getModel().getRowCount();
					++pairid
					)
					{
					PairOfIndividual p=getModel().getPairOfIndividualByPairId(IBDFrame.this.txn,pairid);
					if(!sel.isSelectedIndex(p.get(0).getId())) continue;
					if(!sel.isSelectedIndex(p.get(1).getId())) continue;
					
					selPair.addSelectionInterval(pairid, pairid);
					}
				}
			});
		
		
		JLabel lbl=new JLabel("Treshold:",JLabel.RIGHT);
		toolBar.add(lbl);
		
		JSpinner limit=new JSpinner(new SpinnerNumberModel(DEFAULT_TRESHOLD_LIMIT, 0, 1.0, 0.01));
		lbl.setLabelFor(limit);
		JSpinner.NumberEditor ed=new JSpinner.NumberEditor(limit);
		limit.setEditor(ed);
		limit.setPreferredSize(new Dimension(100,20));

		toolBar.add(limit);
		limit.addChangeListener(new ChangeListener()
			{
			@Override
			public void stateChanged(ChangeEvent e)
				{
				JSpinner limit=JSpinner.class.cast(e.getSource());
				float n=Number.class.cast(SpinnerNumberModel.class.cast(limit.getModel()).getValue()).floatValue();
				for(DataExtractorFactory f:IBDFrame.this.dataExtractorFactories)
					{
					f.setTreshold(n);
					}
				}
			});
		
		}

	/* show internal frame */
	private void showInternalFrame(JInternalFrame f)
		{
		this.desktopPane.add(f);
		Dimension dim=desktopPane.getSize();
		Dimension d2=new Dimension(dim);
		d2.width=(int)(d2.width*0.8);
		d2.height=(int)(d2.height*0.8);
		f.setBounds(
			(int)((dim.width-d2.width)*Math.random()),
			(int)((dim.height-d2.height)*Math.random()),
			d2.width,
			d2.height
			);
		f.setVisible(true);
		}
	
	
	private DataExtractorFactory getSelectedDataExtractorFactory()
		{
		if(this.dataExtractorTypeRadioButton==null) return dataExtractorIBD0Count;
		for(int i=0;i< this.dataExtractorTypeRadioButton.length;++i)
			{
			if(this.dataExtractorTypeRadioButton[i]==null) continue;
			if(this.dataExtractorTypeRadioButton[i].isSelected())
				{
				Object o= dataExtractorTypeRadioButton[i].getClientProperty(DATA_EXTRACTOR_FACTORY);
				if(o==null || !(o instanceof DataExtractorFactory)) continue;
				DataExtractorFactory f= DataExtractorFactory.class.cast(o);
				f.setModel(this.getModel());
				f.setTransaction(null);
				return f;
				}
			}
		return dataExtractorIBD0Count;
		}
	
	
	public Model getModel()
		{
		return this.model;
		}
	
	private void doMenuClose()
		{
		for(JInternalFrame f:this.desktopPane.getAllFrames())
			{
			f.setVisible(false);
			f.dispose();
			}
		this.setVisible(false);
		this.dispose();
		this.model.close();
		}
	
	
	private void doMenuLoadBedData()
		{
		JFileChooser fc=new JFileChooser();
		if(fc.showOpenDialog(this)!=JFileChooser.APPROVE_OPTION) return;
		File f=fc.getSelectedFile();
		BufferedReader in=null;
		List<ChromStartEnd> positions=new ArrayList<ChromStartEnd>();
		try
			{
			Pattern tab=Pattern.compile("[\t]");
			in=IOUtils.open(f);
			String line;
			while((line=in.readLine())!=null)
				{
				if(line.length()==0 || line.startsWith("#")) continue;
				String tokens[]=tab.split(line);
				if(tokens.length<3)
					{
					throw new IOException("Not chrom(tab)start(tab)end  in "+line+"/"+f);
					}
				ChromStartEnd pos=new ChromStartEnd(
					tokens[0],
					Integer.parseInt(tokens[1]),
					Integer.parseInt(tokens[2])
					);
				if(positions.size()==1000 &&
					JOptionPane.showConfirmDialog(this,
					"More that 1000 positions in that file. " +
					"It consummes memory. " +
					"Continue at your own risk ?",
					"Many objects",JOptionPane.OK_CANCEL_OPTION)!=JOptionPane.OK_OPTION	
					)
					{
					return;
					}
				positions.add(pos);
				}

			showInternalFrame(new ChromStartEndFrame(this.txn, f.getName(), positions));
			}
		catch(Exception err)
			{
			JOptionPane.showMessageDialog(this, "Error:"+err.getMessage());
			}
		finally
			{
			IOUtils.safeClose(in);
			}
		}
	
	
	
	
	/** doMenuCreateView */
	private void doMenuCreateView()
		{
		int markerStart=0;
		int markerEnd=0;
		if(this.markersFrame.table.getSelectionModel().isSelectionEmpty())
			{
			markerStart=0;
			markerEnd=this.markersFrame.table.getModel().getRowCount()-1;
			}
		else
			{
			markerStart=this.markersFrame.table.getSelectionModel().getMinSelectionIndex();
			markerEnd=this.markersFrame.table.getSelectionModel().getMaxSelectionIndex();
			}
		LinkageFrame f=new LinkageFrame(
				markerStart,
				markerEnd,
				this.pairsFrame.getSelectedIndexes(),
				(DataExtractorFactory)getSelectedDataExtractorFactory().clone()
				);
		showInternalFrame(f);
		}
	public static void main(String[] args) throws Exception
		{
		LOG.setLevel(Level.OFF);
		
		JFrame.setDefaultLookAndFeelDecorated(true);
		JDialog.setDefaultLookAndFeelDecorated(true);
		Preferences prefs=Preferences.userNodeForPackage(IBDFrame.class);
		int optind=0;
		File dbHome=null;
		
		
		while(optind< args.length)
			{
			if(args[optind].equals("-h"))
				{
				System.err.println("IBDFrame.");
				System.err.println("Options:");
				System.err.println(" -L (level). Log level. Default: "+LOG.getLevel());
				return;
				}
			else if(args[optind].equals("-L"))
				{
				LOG.setLevel(Level.parse(args[++optind]));
				}
			else if(args[optind].equals("--"))
				{
				++optind;
				break;
				}
			else if(args[optind].startsWith("-"))
				{
				System.err.println("Unknown option "+args[optind]);
				}
			else
				{
				break;
				}
			++optind;
			}
		
		if(optind==args.length)
			{
			LOG.info("db not specified");
			String prev=prefs.get("LAST.DB",null);
			JFileChooser f=new JFileChooser(prev==null?null:new File(prev));
			f.setDialogTitle("Select a BerkeleyDB directory");
			f.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
			if(f.showOpenDialog(null)!=JFileChooser.APPROVE_OPTION)
				{
				return;
				}
			dbHome=f.getSelectedFile();
			if(dbHome==null || !dbHome.isDirectory()) return;
			}
		else if(optind+1==args.length)
			{
			LOG.info("db is "+args[optind]);
			dbHome=new File(args[optind]);
			}	
		else
			{
			System.err.println("Illegal number of arguments.\n");
			return;
			}
		final Model m=new Model();
		LOG.info("open model "+dbHome);
		m.open(dbHome, true);
		
		if(dbHome.getParent()!=null)
			{
			prefs.put("LAST.DB", dbHome.getParent());
			}
		Runtime.getRuntime().addShutdownHook(new Thread()
			{
			@Override
			public void run() {
				m.close();
				}
			});
		final IBDFrame frame=new IBDFrame(null,m);
		Dimension screen=Toolkit.getDefaultToolkit().getScreenSize();
		frame.setBounds(50, 50, screen.width-100, screen.height-100);
		
		prefs.sync();
		
		
		SwingUtilities.invokeAndWait(new Runnable()
			{
			@Override
			public void run() {
				frame.setVisible(true);
				}
			});
		}
	}
